﻿/**
 * SlotDuino
 * 
 * Copyright (c) 2011/2012 Thiago Hummel Moreira, Ricardo Mizuuti, Rafael Mufato, Bruno Sakai, Andre Turina
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
using System;
using System.Net;
using System.Text;
using SlotDuino.DAL;
using System.Threading;

namespace SlotDuino.Sockets
{
    /// <summary>
    /// SlotDaemon socket client
    /// </summary>
    public class SlotDaemonClient
    {
        private const int SERVER_PORT = 80;

        #region Private attributes
        /// <summary>
        /// Handles the lower level socket comunication
        /// </summary>
        private TcpClient _tcpClient;
        /// <summary>
        /// Will act as a buffer
        /// </summary>
        private StringBuilder _sb;

        private string _lastResponse;

        private bool _waitingResponse;
        private ManualResetEvent _receiveDone;
        #endregion

        #region Public Properties
        public TcpClient TcpClient
        {
            get { return _tcpClient; }
        }
        
        public TcpClient.ConnectionStatus ConnectionStatus
        {
            get { return _tcpClient.Status; }
        }
        #endregion

        #region Public events
        /// <summary>
        /// Relaying the TcpClient ConnectionStatusChanged event
        /// </summary>
        public event ConnectionStatusChangedEventHandler ConnectionStatusChanged
        {
            add { this._tcpClient.ConnectionStatusChanged += value; }
            remove { this._tcpClient.ConnectionStatusChanged -= value; }
        }
        /// <summary>
        /// This event is fired when a track update is received (new lap, race ended...)
        /// </summary>
        public event EventHandler<CommandReceivedEventArgs> TrackUpdate;
        #endregion

        public SlotDaemonClient()
        {
            //Create a TcpClient
            _tcpClient = new TcpClient();
            //Attach a listener to the TcpClient DataReceived event
            _tcpClient.DataReceived += new DataReceivedEventHandler(OnDataReceived);

            //Create a StringBuilder that expect to hold characters
            _sb = new StringBuilder(150);

            _waitingResponse = false;
            _receiveDone = new ManualResetEvent(false);
        }

        /// <summary>
        /// This method listen the underlaying TcpClient DataReceived event 
        /// and build a string out of it. When it read an entire line, it 
        /// calls the OnCommandReceived method
        /// </summary>
        /// <param name="sender">The TcpClient that received the data</param>
        /// <param name="e">The received data</param>
        private void OnDataReceived(TcpClient sender, DataReceivedEventArgs e)
        {
            //Append the received bytes to the command string
            _sb.Append(Encoding.UTF8.GetChars(e.DataReceived));

            //Check for new lines (our command delimiter)
            string currentString = _sb.ToString();
            int cmdEnd = currentString.IndexOf('\n');
            int removeLen = cmdEnd;
            if (0 <= cmdEnd)
            {
                //Tolerate a \r\n delimiter too
                if ('\r' == currentString[cmdEnd - 1])
                {
                    cmdEnd--;
                    removeLen++;
                }

                //Notify listeners about the new received command
                string command = currentString.Substring(0, cmdEnd);
                OnCommandReceived(command);

                //Remove the command from the string builder
                _sb.Remove(0, removeLen);
            }
        }

        private void OnCommandReceived(string command)
        {
            //If is a track update
            if (command.StartsWith("trackControllerUpdate"))
            {
                var trackUpdateHandler = TrackUpdate;
                if (null != trackUpdateHandler)
                    trackUpdateHandler(this, new CommandReceivedEventArgs(command));
            }
            //Else, it is an previous command response
            else
            {
                _lastResponse = command;
                _receiveDone.Set();
            }
        }
        
        #region Connection control
        public void Connect(IPAddress host)
        {
            _tcpClient.Connect(host, SERVER_PORT);
        }

        public void Disconnect()
        {
            Send("close");
            _tcpClient.Disconnect();
        }

        public void Send(string command)
        {
            //Send a command to server
            _tcpClient.Send(command + "\n");
        }

        public string SendAndWait(string command)
        {
            //Send the command to server
            Send(command);
            //Wait for server response
            _receiveDone.WaitOne();
            //Return response
            return _lastResponse;
        }
        #endregion

        #region Configuration controls
        public void SetConfig(Track track)
        {
            Send(String.Format("setConfig;{0};{1};{2};{3}",
                track.Name,
                track.Length,
                track.Voltages.Count,
                track.Slots.Count
            ));
        }
        
        public DateTime GetTime()
        {
            string response = SendAndWait("getTime");

            return DateTime.Parse(response.Substring(response.IndexOf(':') + 1));
        }

        public void SetTime()
        {
            SetTime(DateTime.Now);
        }
        
        public void SetTime(DateTime dateTime)
        {
            Send("setTime;" + dateTime.ToString("yyyy';'MM';'dd';'HH';'mm';'ss';'fff"));
        }
        #endregion

        #region Race controls
        /// <summary>
        /// Send the needed commands to SlotDaemon to create a race
        /// </summary>
        /// <param name="race">The database Race entity</param>
        public void CreateRace(Race race)
        {
            StringBuilder pilotsIds = new StringBuilder(),
                          lanesIds = new StringBuilder(), 
                          voltagesIds = new StringBuilder();
            
            foreach (RaceSlot raceSlot in race.Slots)
            {
                pilotsIds.AppendFormat("{0}|", raceSlot.PilotId);
                //In SlotDaemon slots start at 0
                lanesIds.AppendFormat("{0}|", (raceSlot.SlotId - 1));
                //In SlotDaemon voltages start at 0
                voltagesIds.AppendFormat("{0}|", (raceSlot.Voltage - 1));
            }

            Send(String.Format("newRace;{0};{1};{2};{3};{4}",
                race.Id,
                race.Duration.TotalMilliseconds, 
                pilotsIds.ToString(0, pilotsIds.Length - 1),
                lanesIds.ToString(0, lanesIds.Length - 1),
                voltagesIds.ToString(0, voltagesIds.Length - 1)
            ));
        }

        public void GetRaces()
        {
        }

        public void GetRaceInfo(Race race)
        {
        }

        public void StartRace(Race race)
        {
            Send(String.Format("startRace;{0}", race.Id));
        }

        public void PauseRace(Race race)
        {
            Send(String.Format("pauseRace;{0}", race.Id));
        }

        public void PauseRaces()
        {
            Send("pauseRaces");
        }

        public void ResumeRace(Race race)
        {
            Send(String.Format("resumeRace;{0}", race.Id));
        }

        public void ResumeRaces()
        {
            Send("pauseRaces");
        }

        public void AbortRace(Race race)
        {
            Send(String.Format("abortRace;{0}", race.Id));
        }

        public void CloseRace(Race race)
        {
            Send(String.Format("closeRace;{0}", race.Id));
        }
        #endregion
    }

    #region Event support classes
    public class CommandReceivedEventArgs : EventArgs
    {
        private string _command;

        #region Public Properties
        /// <summary>
        /// The received command
        /// </summary>
        public string Command { get { return _command; } }
        #endregion

        public CommandReceivedEventArgs(string command)
        {
            _command = command;
        }
    }
    #endregion
}
